#include <QKeyEvent>
#include <QDebug>
#include "dockmanager.h"
#include "defaultwindowfinder.h"
#include "layoutmanager.h"
#include "previewer.h"
#include "basedock.h"
#include "policymanager.h"


DockManager::DockManager():m_dragging(0),m_previewer(0),
    m_last_target(0),
    m_state(State::Default),m_dragtolerance(100){
}

void DockManager::setPreviewer(UDockWidgetPreviewer* previewer){
    // FIXME: shall we do this?
    //    if (m_previewer) {
    //        m_previewer->deleteLater();
    //    }
    Q_ASSERT(previewer);
    m_previewer = previewer;
    previewer->setDockManager(this);
}

void DockManager::setPolicyManager(QPolicyManager *policymanager)
{
    {m_policymanager= policymanager;
        policymanager->setDockManager(this);}
}

void DockManager::keyPressPulling(QKeyEvent* event) {
    int key = event->key();
    if (key == Qt::Key_Shift){
        if (!(m_state & State::Pulling))
            return;
        Q_ASSERT(m_dragging);
        QDockItem *parent = m_dragging->parentDockItem();
        while (parent) {
            if ((m_policymanager->isDetachable(parent)
                           || m_policymanager->isRepositionable(parent) )) {
                m_dragging = parent;
                m_previewer->highlightSource(m_dragging);
                break;
            }
            parent = parent->parentDockItem();
        }
    } else if (key == Qt::Key_Control) {
        if (! (m_state & State::Pulling))
            return;
        Q_ASSERT(m_dragging);
        QDockItem *parent = m_mouse_grab;
        QDockItem *child = m_mouse_grab;
        QDockItem* acceptable_child = 0;
        while (parent != m_dragging) {
            child = parent;
            if (m_policymanager->isDetachable(child)
                    || m_policymanager->isRepositionable(child))
            {
                acceptable_child = child;
            }
            parent = parent->parentDockItem();
            Q_ASSERT(parent);
        }
        child = acceptable_child;
        if (child && m_dragging != child) {
            m_dragging = child;
            m_previewer->highlightSource(m_dragging);
        }
    }
}

void DockManager::keyPressDragging(QDockItem *dw, QKeyEvent *event)
{
    int key = event->key();
    if (key == Qt::Key_Shift){//move up in the tree
        if (m_last_target == 0)
            return;
        if (m_last_target->isWindow())
            return;
        if (qobject_cast<QBaseDock*>(m_last_target)){
            return;
        }

        QDockItem* parent = m_last_target;
        while (parent = parent->parentDockItem()){
            if (m_policymanager->dockOptions(dw, parent)){
                m_last_target = parent;
                m_state = (State)(m_state | State::Shifted);
                mouseMove(dw,m_last_mouse_pos);
                break;
            }
        }
    }
    else if (key == Qt::Key_Control){
        if (!(m_state & State::Shifted))
            return;
        QWidget* child = m_layoutmanager->itemFromPoint(m_last_target, m_last_mouse_pos, false);
        if (!child || child == m_last_target)
            return;
        QWidget* parent = child->parentWidget();
        QDockItem* previous = 0;
        while (parent != m_last_target ){
            if (qobject_cast<QDockItem*>(child)){
                previous = qobject_cast<QDockItem*>(child);
            }
            child = parent;
            parent = child->parentWidget();
        }
        if (previous){
            m_last_target = previous;
            m_state = (State)(m_state | State::Shifted);
            m_previewer->hideTargetDockIndicators();
            mouseMove(dw,m_last_mouse_pos);
        }
    }

}


void DockManager::mouseDown(QDockItem *widget, const QPoint &point, const QPoint &offset){
    m_last_mouse_pos = point;
    m_dragging = widget;
    m_mouse_grab = widget;
    m_offset = offset;
    m_dragstart = point;
    m_previewer->getTargetPreviewWidget()->setFocusProxy(widget);
    m_previewer->getSourcePreviewWidget()->setFocusProxy(widget);
    // TODO: check policy
    if (widget->isWindow()){
        startDrag();
        return mouseMove(widget,point);
    } else {
        startPull();
    }
}


void DockManager::mouseMove(QDockItem *widget, const QPoint &point){
    m_last_mouse_pos = point;
    if (widget != m_mouse_grab)
        return;
    QPoint distance(point);
    distance -= m_dragstart;

    if (m_state & State::Dragging){
        m_previewer->moveWidget(widget, point,m_offset);
        dragging(widget,point);
    }
    else if (distance.manhattanLength() > m_dragtolerance){
        stopPull();
        startDrag();
        return;
    }
    else {
        pulling(widget, point);
        return;
    }
}


void DockManager::dragging(QDockItem *widget, const QPoint &point) {
    updateLastTarget(widget, point);
    if (m_last_target){
        DockOption dockoptions = m_policymanager->dockOptions(widget,m_last_target);
        m_previewer->showDockIndicators(m_last_target, dockoptions);

        DockOption dockoption = m_previewer->hoveredIndicator(point, m_last_target);
        if (dockoption != 0){
            //TODO: highlight docked
            //            qDebug() << dockoption;
            QRect rec = m_layoutmanager->dockedSize(widget, m_last_target, dockoption);
            m_previewer->highlightDocked(m_last_target, rec);
        }
        else {
            m_previewer->unHighlightDocked();
        }
        if (m_last_target){
            m_previewer->highlightTarget(m_last_target);
        }
    }
}

void DockManager::updateLastTarget(QDockItem *widget, const QPoint &point)
{
    const std::unordered_set<Window>& ignores = this->ignores(widget);
    Window target_window = UDock::DefaultWindowFinder::getDefaultWindowFinder()
            ->windowFromPoint(UDock::Point(point.x(),point.y()),ignores);
    //    qDebug()<< target_window;
    //TODO: show dock indicators
    QDockItem* basedock_target = m_layoutmanager->baseDockFromPoint(target_window,point);

    if (basedock_target == 0){
        m_previewer->unHover();
        m_last_target = 0;
        m_state = (State)(m_state & ~State::Shifted);
        return;
    }
    QDockItem* dockitem_target = m_layoutmanager->itemFromPoint(basedock_target, point);
    if (dockitem_target){
        if (m_state & State::Shifted){
            if (m_last_target
                    && m_last_target->isAncestorOf(dockitem_target)){
                dockitem_target = m_last_target;
            }
            else {
                m_last_target = 0;
                m_state = (State)(m_state & ~State::Shifted);
            }
        }
        m_last_target = dockitem_target;
    }
}

void DockManager::pulling(QDockItem *widget, const QPoint &point) {
    // currently do nothing
}

void DockManager::mouseUp(QDockItem *widget, const QPoint &point){
    m_previewer->getTargetPreviewWidget()->setFocusProxy(0);
    m_previewer->getSourcePreviewWidget()->setFocusProxy(0);
    if (m_state & State::Dragging) {
        m_last_mouse_pos = point;
        mouseMove(widget,point);
        DockOption dockoption = m_previewer->hoveredIndicator(point, m_last_target);
        if (dockoption != 0){
            qDebug() << dockoption;
            layoutManager()->dock(widget, m_last_target, dockoption);
        }
        else {
            layoutManager()->addToplevelDock(widget);
        }

        QPoint pt = point;
        pt -= m_offset;
        widget->move(pt);
        stopDrag();
    } else if (m_state & State::Pulling) {
        // do not change layout, as the widget was never dragged.
        stopPull();
        // reset mouse grab and dragging, since we won't start dragging
        m_dragging = 0;
        m_mouse_grab = 0;
    }
}


void DockManager::keyPress(QDockItem *dw, QKeyEvent *event)
{

    if (!(m_state & State::Dragging)) {
        return keyPressPulling(event);
    }
    else {
        return keyPressDragging(dw, event);
    }
}

void DockManager::startPull() {
    m_state = static_cast<State>(m_state | State::Pulling);
    connect(m_mouse_grab, SIGNAL(keyPress(QDockItem*,QKeyEvent*))
            ,this,SLOT(keyPress(QDockItem*,QKeyEvent*)));
    m_previewer->highlightSource(m_dragging);

}

void DockManager::stopPull() {
    m_state = static_cast<State>(m_state & ~State::Pulling);
    m_previewer->unHighlightSource();
    disconnect(m_mouse_grab, SIGNAL(keyPress(QDockItem*,QKeyEvent*))
               ,this,SLOT(keyPress(QDockItem*,QKeyEvent*)));
}

void DockManager::startDrag()
{
    m_state = static_cast<State>(m_state | State::Dragging);
    m_previewer->previewBasicDocks(m_dragging);
    connect(m_mouse_grab, SIGNAL(keyPress(QDockItem*,QKeyEvent*))
            ,this,SLOT(keyPress(QDockItem*,QKeyEvent*)));
}

void DockManager::stopDrag()
{
    m_last_target = 0;
    disconnect(m_mouse_grab, SIGNAL(keyPress(QDockItem*,QKeyEvent*))
               ,this,SLOT(keyPress(QDockItem*,QKeyEvent*)));
    m_previewer->hidePreviews();
    m_dragging = 0;
    m_mouse_grab = 0;
    m_state = static_cast<State>(m_state & ~(State::Dragging | State::Shifted));

}

std::unordered_set<Window> DockManager::ignores(QWidget* widget) const
{
    std::unordered_set<Window> ignores = previewer()->previewWidgets();
    ignores.insert(widget->winId());
    return ignores;
}
